package jvs.vfs.tools;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.UIManager;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import javax.swing.text.TextAction;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

/**
 * 
 * @author qiangli
 * 
 */
public class TextEditor extends JApplet {
	private static final long serialVersionUID = 1L;

	public static void main(String[] args) {
		JFrame frame = new JFrame();
		frame.setTitle("Text Editor");
		frame.setBackground(Color.lightGray);
		frame.getContentPane().setLayout(new BorderLayout());
		frame.getContentPane().add("Center", new TextEditor());
		frame.addWindowListener(new WinAdapter());
		frame.pack();
		frame.setSize(600, 450);
		frame.setVisible(true);
	}

	private Hashtable<Object, Action> commands;
	private Hashtable<Object, JMenuItem> menuitems;

	private JMenuBar menubar;
	private JTextComponent editor;
	private JComponent statusbar;

	private static String[] filemenu = { "File", "Open", "Save", "-", "Exit" };
	private static String[] editmenu = { "Edit", "Undo", "-", "Cut", "Copy",
			"Paste", "-", "Select All" };
	private static String[] helpmenu = { "Help", "About" };

	private UndoManager undo = new UndoManager();
	private UndoableEditListener undolistener = new UndoListener();

	private Action[] actions = { new NewAction(), new OpenAction(),
			new SaveAction(), new ExitAction(), new UndoRedoAction(),
			new AboutAction() };

	private static String[][] defaultactions = { { "Cut", "cut-to-clipboard" },
			{ "Copy", "copy-to-clipboard" },
			{ "Paste", "paste-from-clipboard" }, { "Select All", "select-all" } };

	private File currentFile;

	private boolean save;

	public TextEditor() {
		try {
			UIManager.setLookAndFeel(UIManager
					.getCrossPlatformLookAndFeelClassName());
		} catch (Exception e) {
		}

		editor = createEditor();

		commands = new Hashtable<Object, Action>();
		menuitems = new Hashtable<Object, JMenuItem>();

		Action[] actions = getActions();
		for (int i = 0; i < actions.length; i++) {
			putAction(actions[i]);
		}

		menubar = createMenubar();

		statusbar = createStatusbar();

		Container pane = getContentPane();
		pane.setLayout(new BorderLayout());

		JScrollPane scroller = new JScrollPane();
		JViewport port = scroller.getViewport();
		port.setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE);

		editor.setFont(new Font("monospaced", Font.PLAIN, 12));
		editor.getDocument().addUndoableEditListener(undolistener);
		port.add(editor);

		pane.add("North", menubar);
		JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout());
		panel.add("Center", scroller);
		pane.add("Center", panel);
		pane.add("South", statusbar);

		disableEditor();
		// enableEditor();
		enableMenuItem("Open");
		enableMenuItem("Exit");
		enableMenuItem("About");
	}

	private Frame getFrame() {
		for (Container c = getParent(); c != null; c = c.getParent()) {
			if (c instanceof Frame) {
				return (Frame) c;
			}
		}
		return null;
	}

	private JTextComponent createEditor() {
		return new JTextArea();
	}

	private void disableEditor() {
		editor.setEditable(false);
	}

	private void enableEditor() {
		Enumeration<JMenuItem> me = menuitems.elements();
		while (me.hasMoreElements()) {
			((JMenuItem) me.nextElement()).setEnabled(true);
		}
		editor.setEditable(true);
	}

	private void putAction(Action a) {
		try {
			commands.put(a.getValue(Action.NAME), a);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}

	private Action getAction(String name) {
		for (int i = 0; i < defaultactions.length; i++) {
			if (defaultactions[i][0].equals(name)) {
				name = defaultactions[i][1];
				break;
			}
		}
		return (Action) commands.get(name);
	}

	private Action[] getActions() {
		Action[] a = TextAction.augmentList(editor.getActions(), actions);
		return a;
	}

	private JMenuBar createMenubar() {
		JMenuBar mb = new JMenuBar();
		mb.add(createMenu(filemenu));
		mb.add(createMenu(editmenu));
		mb.add(createMenu(helpmenu));
		return mb;
	}

	private JMenu createMenu(String[] items) {
		JMenu menu = new JMenu(items[0]);
		for (int i = 1; i < items.length; i++) {
			if (items[i].equals("-")) {
				menu.addSeparator();
			} else if (items[i].charAt(0) == '>') { // create sub menu
				JMenu sub = new JMenu(items[i++].substring(1));
				while (i < items.length && items[i].charAt(0) == '|') {
					JMenuItem mi = createMenuItem(items[i++].substring(1));
					sub.add(mi);
				}
				menu.add(sub);
				i--;
			} else {
				JMenuItem mi = createMenuItem(items[i]);
				menu.add(mi);
			}
		}
		return menu;
	}

	protected JMenuItem createMenuItem(String cmd) {
		JMenuItem mi = new JMenuItem(cmd);
		mi.setActionCommand(cmd);
		Action a = getAction(cmd);
		if (a != null) {
			mi.addActionListener(a);
			mi.setEnabled(false);
		} else {
			mi.setEnabled(false);
		}
		menuitems.put(cmd, mi);
		return mi;
	}

	private JMenuItem getMenuItem(String cmd) {
		return (JMenuItem) menuitems.get(cmd);
	}

	private void enableMenuItem(String cmd) {
		JMenuItem mi = (JMenuItem) menuitems.get(cmd);
		mi.setEnabled(true);
	}

	private JComponent createStatusbar() {
		statusbar = new StatusBar();
		return statusbar;
	}

	// Actions

	class NewAction extends AbstractAction {
		private static final long serialVersionUID = 1L;

		NewAction() {
			super("New");
		}

		public void actionPerformed(ActionEvent e) {
			if (editor.getDocument() != null) {
				editor.getDocument().removeUndoableEditListener(undolistener);
			}
			editor.setDocument(new PlainDocument());
			editor.getDocument().addUndoableEditListener(undolistener);
			validate();
		}
	}

	class FileLoader extends Thread {
		Document doc;
		File file;

		FileLoader(File file, Document doc) {
			this.file = file;
			this.doc = doc;
		}

		public void run() {
			try {
				statusbar.removeAll();
				JProgressBar progress = new JProgressBar();
				progress.setMinimum(0);
				progress.setMaximum((int) file.length());
				statusbar.add(progress);
				statusbar.revalidate();

				Reader in = new InputStreamReader(new FileInputStream(file));
				char[] buf = new char[4096];
				int cnt;
				while ((cnt = in.read(buf, 0, buf.length)) != -1) {
					doc.insertString(doc.getLength(), new String(buf, 0, cnt),
							null);
					progress.setValue(progress.getValue() + cnt);
				}

				doc.addUndoableEditListener(undolistener);
				statusbar.removeAll();
				statusbar.revalidate();
				//
				enableEditor();
			} catch (IOException e) {
			} catch (BadLocationException e) {
			}
		}
	}

	private void openFile(File file) {
		if (file == null) {
			String name = System.getProperty("user.home", "/");
			file = new File(name);
		}
		JFileChooser chooser = null;

		try {
			chooser = new JFileChooser(file);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
		Frame frame = getFrame();
		int rc = chooser.showOpenDialog(frame);

		if (rc == JFileChooser.APPROVE_OPTION) {
			file = (File) chooser.getSelectedFile();

		} else {
			return;
		}
		if (file.exists()) {
			Document old = editor.getDocument();
			if (old != null) {
				old.removeUndoableEditListener(undolistener);
			}
			editor.setDocument(new PlainDocument());
			frame.setTitle(file.getName());
			Thread loader = new FileLoader(file, editor.getDocument());
			loader.start();

			currentFile = file;
			save = false;
		}
	}

	class OpenAction extends AbstractAction {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		OpenAction() {
			super("Open");
		}

		public void actionPerformed(ActionEvent ae) {
			openFile(currentFile);
		}
	}

	private void saveFile(File file) {
		if (file == null) {
			return;
		}
		if (file.exists()) {
			try {
				Writer o = new OutputStreamWriter(new FileOutputStream(file));
				o.write(editor.getText());
				o.flush();
				o.close();

				// changeMenuItemAction("SaveExit", "Exit");
				save = false;
			} catch (IOException io) {
			}
		}
	}

	class SaveAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		SaveAction() {
			super("Save");
		}

		public void actionPerformed(ActionEvent ae) {
			saveFile(currentFile);
		}
	}

	class ExitAction extends AbstractAction {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		ExitAction() {
			super("Exit");
		}

		public void actionPerformed(ActionEvent ae) {
			System.out.println("Exit");
			if (save) {
				int rc = JOptionPane.showConfirmDialog(getFrame(),
						"Save changes?", "", JOptionPane.YES_NO_OPTION);
				if (rc == JOptionPane.OK_OPTION) {
					saveFile(currentFile);
					System.out.println("saved!!!");
				}
			}
			System.exit(0);
		}
	}

	class UndoRedoAction extends AbstractAction {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		public UndoRedoAction() {
			super("Undo");
		}

		public void actionPerformed(ActionEvent ae) {
			String cmd = ae.getActionCommand();
			String act = (String) getAction(cmd).getValue(Action.NAME);
			System.out.println("Undo/Redo: " + act);
			try {
				if (act.equals("Undo")) {
					undo.undo();
					putValue(Action.NAME, "Redo");
					getMenuItem(cmd).setText("Redo");
				} else if (act.equals("Redo")) {
					undo.redo();
					putValue(Action.NAME, "Undo");
					getMenuItem(cmd).setText("Undo");
				}
			} catch (CannotUndoException e) {
				putValue(Action.NAME, "Redo");
				getMenuItem("Undo").setText("Redo");
			} catch (CannotRedoException e) {
				putValue(Action.NAME, "Undo");
				getMenuItem("Undo").setText("Undo");
			}
		}
	}

	class AboutAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		AboutAction() {
			super("About");
		}

		public void actionPerformed(ActionEvent ae) {
			JOptionPane.showMessageDialog(getFrame(),
					"Text Editor 1.0\nCopyright (C) 2000\nLi, Qiang\n",
					"About", JOptionPane.INFORMATION_MESSAGE);
		}
	}

	class StatusBar extends JComponent {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		public StatusBar() {
			super();
			setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
		}

		public void paint(Graphics g) {
			super.paint(g);
		}
	}

	// Listeners
	class UndoListener implements UndoableEditListener {
		public void undoableEditHappened(UndoableEditEvent e) {
			undo.addEdit(e.getEdit());
			// undoredoaction.setEnabled(true);
			getAction("Undo").putValue(Action.NAME, "Undo");
			getMenuItem("Undo").setText("Undo");
			// changeMenuItemAction("Exit", "SaveExit");
			System.out.println("Undo: " + e.toString());
			save = true;
		}
	}

	private static class WinAdapter extends WindowAdapter {
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}
}